import sys
import configparser
import time
import udsoncan
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QLineEdit
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5 import uic
import can
from isotp import TransportLayer, Address, AddressingMode, TransportLayerLogic, CanStack
from qtpy import QtWidgets
from udsoncan.client import Client
from udsoncan.connections import PythonIsoTpConnection
from PCANBasic import *  ## PCAN-Basic library import
from qtmodern.styles import dark, light


ecuXorArray = [0x47, 0x53, 0x4D, 0x4B]
ecuXorArray60 = [0x4B, 0x4D, 0x53, 0x47]


# 读取ini文件
class ConfigReader:
    def __init__(self, config_file):
        self.config = configparser.ConfigParser()
        try:
            self.config.read(config_file)
            self.request_id = int(self.config.get('CAN', 'RequestID'), 16)  # 请求ID
            self.response_id = int(self.config.get('CAN', 'ResponseID'), 16)  # 响应ID
            self.Bitrate = int(self.config.get('CAN', 'Bitrate'))  # 波特率
            self.dids = self.read_did()  # 新增dids属性

        except Exception as e:
            raise ValueError(f"Error reading config file: {e}")

    def read_did(self):
        try:
            # 初始化一个空字典来存储结果
            result_dict = {}

            # 遍历所有的section
            for section in self.config.sections():
                if section.startswith('0x'):
                    # 获取section中的type和length信息
                    section_type = self.config.get(section, 'type')
                    length = int(self.config.get(section, 'length'))

                    if section_type == 'hex':
                        # 根据长度生成对应的格式字符串
                        format_str = 'B' * length
                        # 创建DidCodec对象
                        codec = udsoncan.DidCodec(format_str)
                    elif section_type == 'ascii':
                        # 创建AsciiCodec对象
                        codec = udsoncan.AsciiCodec(length)
                    else:
                        continue

                    # 将十六进制字符串转换为整数
                    key = int(section, 16)
                    # 将键值对添加到结果字典中
                    result_dict[key] = codec

            return result_dict
        except Exception as e:
            raise ValueError(f"Error reading config file: {e}")


class MultiDIDWorker(QThread):
    signal = pyqtSignal(str)

    def __init__(self, dir_list, canbus, ReadDid):
        QThread.__init__(self)
        self.dir_list = dir_list
        self.can_bus = canbus
        self.ReadDid = ReadDid

    def run(self):
        try:
            self.signal.emit("Connecting to PCAN for multi DID read...")
            # 遍历配置中的DID
            for did in self.dir_list:
                # 构建请求数据
                self.signal.emit(f"Sent request for DID: 0x{did:04X}")
                self.ReadDid(did)  # 发送请求
                time.sleep(0.1)

        except Exception as e:
            self.signal.emit(f"Error: {e}")


class CAN_Receive(QThread):
    signal = pyqtSignal(str)

    def __init__(self, can_bus, open_pcan_flag
                 ):
        QThread.__init__(self)
        self.can_bus = can_bus
        self.open_pcan_flag = open_pcan_flag

    def run(self):
        try:
            while self.open_pcan_flag:
                msg = self.can_bus.recv()
                if msg.data() is None:
                    self.signal.emit("RX:{}".format(msg))
                time.sleep(0.01)
            # 发送数据
        except Exception as e:
            self.signal.emit(f"Error: {e}")


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.Ui = uic.loadUi("DID reader.ui", self)
        self.rev = None
        self.canbus = None
        self.open_pcan_flag = False
        self.isCh1Open = False  # 新增isCh1Open属性
        self.receive_data_flag = False
        self.extended = False
        self.m_objPCANBasic = PCANBasic()
        self.m_PcanHandle = PCAN_USBBUS1
        self.initUI()

    # 按钮事件
    def initUI(self):
        # 文本框
        self.input_textbox = self.Ui.lineEdit
        # 修改 self.input_textbox 尺寸
        self.result_textbox = self.Ui.textBrowser
        self.result_textbox.setReadOnly(True)
        # 连接按钮事件
        self.Ui.pushButton.clicked.connect(self.connect_pcan)  # 连接PCAN
        self.Ui.pushButton_2.clicked.connect(self.disconnect_pcan)  # 断开PCAN
        self.Ui.pushButton_3.clicked.connect(self.send_data_tp)  # 发送数据
        self.Ui.pushButton_4.clicked.connect(self.read_multi_dids)  # 读取多个DID
        self.Ui.pushButton_5.clicked.connect(self.read_did)  # 接收数据
        self.Ui.pushButton_6.clicked.connect(self.clear_text)  # 清除文本
        # 连接按钮事件
        self.Ui.setWindowTitle('CAN Read DID Tool')
        self.Ui.setGeometry(300, 300, 800, 600)
        # 设置半透明背景
        self.setWindowOpacity(0.9)



    # 清除文本
    def clear_text(self):
        self.result_textbox.clear()

    # 读取单个DID
    def read_did(self):
        try:
            if self.open_pcan_flag:
                if self.Ui.comboBox.currentText() != '':
                    data = self.Ui.comboBox.currentText()
                    # 去除 0x 前缀
                    if data.startswith('0x'):
                        data = data[2:]
                    try:
                        data_bytes = bytes.fromhex(data)
                        # 将 bytes 对象转换为整数
                        data_int = int.from_bytes(data_bytes, byteorder='big')
                        self.result_textbox.append("TX: {}".format(data_bytes.hex()))
                        self.ReadDid(data_int)
                    except ValueError:
                        self.result_textbox.append("The input is not a valid hexadecimal string.")
                else:
                    self.result_textbox.append("Please select a DID.")
            else:
                self.result_textbox.append("Please connect to PCAN first.")
        except Exception as e:
            self.result_textbox.append(f"Error: {e}")

    # 链接P前奏
    def connect_pcan(self):
        try:
            try:
                self.config = ConfigReader('config.ini')
                self.DID = self.config.dids  # 修正为self.config.dids
                self.Ui.comboBox.clear()
                if self.config.request_id > 0x7FF:
                    self.extended = True
                    print('扩展帧类型')
                    self.result_textbox.append("Extended Frame Type")
                else:
                    self.extended = False
                    print('标准帧类型')
                    self.result_textbox.append("Standard Frame Type")
                for did in self.DID:
                    self.Ui.comboBox.addItem(f"0x{did:04X}")
            except ValueError as e:
                print(f"Error: {e}")
                sys.exit(1)
            self.isotp_params = {
                'stmin': 32,
                'blocksize': 8,
                'wftmax': 0,
                'tx_data_length': 8,
                'tx_padding': 0,
                'rx_flowcontrol_timeout': 1000,
                'rx_consecutive_frame_timeout': 1000,
            }
            if not self.open_pcan_flag:
                self.open_pcan_flag = True
                self.result_textbox.append("Connecting to PCAN...")
                self.canbus = openPCAN(self.config.Bitrate, self.addrees_extended_frame_judge(self.extended)[0])  # 打开通道1
                self.Connect_action()  # 连接PCAN
                self.result_textbox.append("PCAN connected.")
                #self.Receive_data()  # 打开的时候就接收数据，会导致后面发送数据时一直在接收数据
                self.Ui.pushButton.setEnabled(False)
        except Exception as e:
            self.result_textbox.append(f"Error: {e}")

    # 判断是否是扩展帧

    def addrees_extended_frame_judge(self, extended):
        if extended == True:
            return True,AddressingMode.Normal_29bits
        else:
            return False,AddressingMode.Normal_11bits

    # 链接PCAN
    def Connect_action(self):
        try:
            dir = self.config.read_did()  # 读取DID
            # 开启协议栈

            self._isotpaddr_PHYS = Address(self.addrees_extended_frame_judge(self.extended)[1], txid=self.config.request_id,
                                           rxid=self.config.response_id)  # 网络层寻址方法
            self.tp_stack = CanStack(bus=self.canbus, address=self._isotpaddr_PHYS,
                                     params=self.isotp_params)  # 网络/传输层（IsoTP 协议）
            self.conn = PythonIsoTpConnection(self.tp_stack)  # 应用层和传输层之间建立连接
            self.config = dict(udsoncan.configs.default_client_config)
            self.config['security_algo'] = self.SecAlgo  # 安全算法
            self.config['security_algo_params'] = dict(INITIAL_REMINDER=0xFFFE)
            self.config['data_identifiers'] = dir  # DID数据标识符
            self.config['input_output'] = {0x3B90: udsoncan.DidCodec('B'), }
            self.config['server_address_format'] = 32
            self.config['server_memorysize_format'] = 32
            self.config['standard_version'] = 2006
            # self.config['use_server_timing'] = True
            self.config['request_timeout'] = 5
            self.config['dtc_snapshot_did_size'] = 0x01

            with Client(self.conn, config=self.config, request_timeout=2) as self.udsclient:
                pass
                # self.conn.open()  # 打开连接 已规划在 read函数中打开连接
                # self.conn.close()

            # Prepares the PCAN-Basic's PCAN-Trace file
            self.isCh1Open = True
            print('Is open的状态是：', self.isCh1Open)
            print("通道1已连接")
            # self.result_textbox.setText("提示：通道1已连接！")
            self.result_textbox.ensureCursorVisible()
            self.result_textbox.moveCursor(self.result_textbox.textCursor().End)

            if self.open_pcan_flag == True:
                self.result_textbox.ensureCursorVisible()
                # self.result_textbox.append("提示：CAN通讯已连接！")
                self.result_textbox.moveCursor(self.result_textbox.textCursor().End)

        except Exception as err:
            print('出现问题')
            print(err)

    # 没有使用
    def Receive_data(self):
        try:
            if self.open_pcan_flag == True and self.receive_data_flag == False:
                self.receive_data_flag = True
                self.rev = CAN_Receive(self.canbus, self.open_pcan_flag)
                self.rev.signal.connect(self.update_result_textbox)
                self.rev.start()

            else:
                self.result_textbox.append("提示：通道未打开，请先打开通道！")
                self.result_textbox.ensureCursorVisible()
                self.result_textbox.moveCursor(self.result_textbox.textCursor().End)

        except Exception as e:
            print('接收请求失败')
            self.result_textbox.append(f'读取失败:{str(e)}')

    # 读取DID的操作
    def ReadDid(self, DID):
        if self.isCh1Open == True:

            # DID 打印十六进制数
            # re = int(DID, 16)
            print("开始读取DID:", hex(DID))
            if not self.conn.is_open():
                self.conn.open()  # 打开连接
            try:
                self.udsclient.change_session(1)

                resp = self.udsclient.read_data_by_identifier([DID])  # 读取DID
                UDS_str = resp.service_data.values[DID]  # 读取DID的值(f返回值)
                print('UDS发送请求成功')
                self.conn.close()
                # 将整数转换为十六进制字符串
                if isinstance(UDS_str, int):
                    UDS_str_hex = hex(UDS_str)
                else:
                    UDS_str_hex = str(UDS_str)  # 如果不是整数，保持原样
                self.result_textbox.append(f"The result of reading DID{hex(DID)}is：")
                self.result_textbox.ensureCursorVisible()
                self.result_textbox.append(UDS_str_hex)
                self.result_textbox.ensureCursorVisible()
                self.result_textbox.moveCursor(self.result_textbox.textCursor().End)
            except Exception as e:
                print('UDS发送请求失败')
                self.result_textbox.append(f'DID reading failed:{str(e)}')

        else:
            self.result_textbox.append("Tip: The channel is not open. Please open the channel first!")
            self.result_textbox.ensureCursorVisible()
            self.result_textbox.moveCursor(self.result_textbox.textCursor().End)

    # 断开PCAN
    def disconnect_pcan(self):
        try:
            if self.open_pcan_flag:
                self.open_pcan_flag = False
                self.isCh1Open = False  # 断开连接时设置isCh1Open为False
                self.result_textbox.append("PCAN disconnected.")
                self.Ui.pushButton.setEnabled(True)

            if self.receive_data_flag == True and self.rev.isRunning() == True:
                self.receive_data_flag = False
                self.rev.terminate()

                self.result_textbox.append("Receive data stopped.")
            self.canbus.shutdown()  # 关闭通道1
            self.result_textbox.append("PCAN closed.")
            self.result_textbox.ensureCursorVisible()
            self.result_textbox.moveCursor(self.result_textbox.textCursor().End)
        except Exception as e:
            self.result_textbox.append(f"Error: {e}")

    # 判断 data的前一个字节是否是0x22
    def judge_input_data(self,data):

        if data[0] == 0x22:
            self.result_textbox.append("TX: {}".format('检测到0x22 读取DID功能码'))
            return 0x22
        elif data[0] == 0x2E:
            self.result_textbox.append("TX: {}".format('检测到0x2E 写DID功能码'))
            return 0x2E
        else:
            self.result_textbox.append("TX: {}".format('检测到未知功能码'))
            return False

    def test(self,data):
        re = self.tp_stack.send(data)
        time.sleep(0.1)
        # 接收响应
        response = self.tp_stack.recv(timeout=5)  # 设置超时时间为2秒
        print(response)
        # 打印响应
        if response:
            self.result_textbox.append(f"RX: {response}")
        else:
            self.result_textbox.append("RX: 未收到响应")
    # 发送按钮 发送文本框内的数据
    def send_data_tp(self):
        try:
            if self.open_pcan_flag:
                input_text = self.input_textbox.text().replace(" ", "")
                print(type(input_text))
                # 这里需要将输入的文本转换为CAN消息
                if self.Ui.lineEdit.text():

                    try:
                        data = bytes.fromhex(input_text)

                        self.result_textbox.append("TX: {}".format(data.hex()))
                        # 使用ISO-TP协议发送数据
                        if not self.conn.is_open():
                            self.conn.open()  # 打开连接
                        try:
                            re = self.tp_stack.send(data)
                            time.sleep(0.1)
                            # 接收响应
                            response = self.tp_stack.recv(timeout=5)  # 设置超时时间为2秒
                            print(response)
                            # 打印响应
                            if response:
                                self.result_textbox.append(f"RX: {response.hex()}")
                            else:
                                self.result_textbox.append("RX: No response received")

                        except Exception as e:
                            self.result_textbox.append(f"ISO-TP communication error: {e}")
                        finally:
                            if self.conn.is_open():
                                self.conn.close()  # 关闭连接

                    except ValueError:
                        self.result_textbox.append("The input text is not a valid hexadecimal string!")
                else:
                    self.result_textbox.append("The input text cannot be empty!")

            else:
                self.result_textbox.append("Tip: The channel is not open, please open the channel first")
                self.result_textbox.ensureCursorVisible()
                self.result_textbox.moveCursor(self.result_textbox.textCursor().End)
        except Exception as e:
            self.result_textbox.append(f"Error: {e}")

    # 报文接收函数
    def isotp_rcv(self):
        # We execute the "Read" function of the PCANBasic
        result = self.m_objPCANBasic.Read(self.m_PcanHandle)
        if result[0] == PCAN_ERROR_OK:
            # We show the received message
            RevMsg = TPCANMsg()
            RevMsg.ID = result[1].ID
            RevMsg.DLC = result[1].LEN
            for i in range(8 if (result[1].LEN > 8) else result[1].LEN):
                RevMsg.DATA[i] = result[1].DATA[i]
            RevMsg.MSGTYPE = result[1].MSGTYPE
        else:
            RevMsg = None
        return RevMsg
    # 读取多个DID
    def read_multi_dids(self):
        try:
            if self.open_pcan_flag:
                self.worker = MultiDIDWorker(self.DID, self.canbus, self.ReadDid)
                self.worker.signal.connect(self.update_result_textbox)
                self.worker.start()
            else:
                self.result_textbox.append("Tip: The channel is not open, please open the channel first")
                self.result_textbox.ensureCursorVisible()
                self.result_textbox.moveCursor(self.result_textbox.textCursor().End)
        except Exception as e:
            self.result_textbox.append(f"Error: {e}")

    # 大文本框的更新
    def update_result_textbox(self, message):
        self.result_textbox.append(message)

    # Seed计算方法

    def SecAlgo(self, level, seed):
        print("SecAlgo")
        print("level=", level)
        bSeed = seed
        Cal = []
        Key = [0, 0, 0, 0]
        if level == 0x01:
            print("level01")
            for i in range(4):
                Cal.append(bSeed[i] ^ ecuXorArray[i])
            Key[0] = (((Cal[2] & 0xF0) << 4) | (Cal[3] & 0xF0)) & 0xFF
            Key[1] = (((Cal[3] & 0x2F) << 2) | (Cal[1] & 0x03)) & 0xFF
            Key[2] = (((Cal[1] & 0xFC) >> 2) | (Cal[0] & 0xC0)) & 0xFF
            Key[3] = (((Cal[0] & 0x0F) << 4) | (Cal[2] & 0x0F)) & 0xFF
            remainder = (Key[0] << 24) + (Key[1] << 16) + (Key[2] << 8) + Key[3]
            output_key = (remainder).to_bytes(4, 'big', signed=False)

        elif level == 0x61:
            print("level61")
            for i in range(4):
                Cal.append(bSeed[i] ^ ecuXorArray60[i])
            Key[0] = (((Cal[1] & 0x7F) << 2) | (Cal[2] & 0xF0)) & 0xFF
            Key[1] = (((Cal[0] & 0xEC) >> 2) | (Cal[1] & 0x7E)) & 0xFF
            Key[2] = (((Cal[3] & 0xF0) >> 4) | (Cal[0] & 0x2C)) & 0xFF
            Key[3] = (((Cal[2] & 0x0F) << 4) | (Cal[3] & 0x7d)) & 0xFF
            remainder = (Key[0] << 24) + (Key[1] << 16) + (Key[2] << 8) + Key[3]
            output_key = (remainder).to_bytes(4, 'big', signed=False)
            print("output_key=", output_key)

        elif level == 0x09:
            print("level09")
            for i in range(4):
                Cal.append(bSeed[i] ^ ecuXorArray[i])
            Key[0] = (((Cal[1] & 0x7F) << 2) | (Cal[2] & 0xF0)) & 0xFF
            Key[1] = (((Cal[0] & 0xEC) >> 2) | (Cal[1] & 0x7E)) & 0xFF
            Key[2] = (((Cal[3] & 0xF0) >> 4) | (Cal[0] & 0x2C)) & 0xFF
            Key[3] = (((Cal[2] & 0x0F) << 4) | (Cal[3] & 0x7d)) & 0xFF
            remainder = (Key[0] << 24) + (Key[1] << 16) + (Key[2] << 8) + Key[3]
            output_key = (remainder).to_bytes(4, 'big', signed=False)
        return output_key
    # -----


def openPCAN(bit_rate, extended_id):
    can0 = can.interface.Bus(interface='pcan',channel='PCAN_USBBUS1', bitrate=bit_rate, extended_id=extended_id)


    return can0


if __name__ == '__main__':
    app = QApplication(sys.argv)
    # 白天7点到晚上7点为dark,晚上为 light
    import datetime
    now = datetime.datetime.now()
    if now.hour >= 9 and now.hour < 16:
        dark(app)
    else:
        light(app)

    # dark(app)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
